package native

import (
	"log"
	"unsafe"

	"gitee.com/microwww/jvm-go/jvm/class"
	"gitee.com/microwww/jvm-go/jvm/rtda"
)

type NativesMethod func(*rtda.MethodFrame)

var nativesMethods = make(map[string]NativesMethod, 255)

func GetMethod(method *class.Method) (NativesMethod, bool) {
	nm := nativesMethods[method.Class().Name()+"."+method.Name()+method.Descriptor()]
	if nm == nil {
		// panic(fmt.Sprintf("Not find native method %v.%v%v", method.Class().Name(), method.Name(), method.Descriptor()))
		return nil, false
	}
	return nm, true
}

func init() {
	Register("java/lang/Class", "registerNatives", "()V", classRegisterNatives)
	Register("java/lang/System", "arraycopy", "(Ljava/lang/Object;ILjava/lang/Object;II)V", arraycopy)
	Register("java/lang/Object", "hashCode", "()I", javaLangObject_hashCode)
	Register("java/lang/Thread", "currentThread", "()Ljava/lang/Thread;", javaLangThread_currentThread)
	// Register("java/lang/System", "registerNatives", "()V", javaLangSystem_registerNatives)
}

func Register(clazz, method, descriptor string, fun NativesMethod) {
	nativesMethods[clazz+"."+method+descriptor] = fun
}

// java/lang/Class.registerNatives()V
func classRegisterNatives(frame *rtda.MethodFrame) {
	frame.Thread().PopMethodFrame()
}

// java/lang/System", "arraycopy", "(Ljava/lang/Object; ILjava/lang/Object; II) V

//native.Register("java/lang/System", "arraycopy", "(Ljava/lang/Object; I Ljava/lang/Object; I I) V", arraycopy)
func arraycopy(frame *rtda.MethodFrame) {
	vars := frame.LocalVar()
	src := vars.GetRef(0)
	srcPos := vars.GetInt(1)
	dest := vars.GetRef(2)
	destPos := vars.GetInt(3)
	length := vars.GetInt(4)
	if src == nil || dest == nil {
		panic("java.lang.NullPointerException")
	}
	if srcPos < 0 || destPos < 0 || length < 0 || srcPos+length > src.ArrayLength() || destPos+length > dest.ArrayLength() {
		panic(" java.lang.IndexOutOfBoundsException")
	}
	ArrayCopy(src, dest, srcPos, destPos, length)
}

func ArrayCopy(src, dst *class.Object, srcPos, dstPos, length int32) {
	if src.IsArray() && dst.IsArray() {
		switch t := dst.Data().(type) {
		case []int8:
			_src := src.Data().([]int8)[srcPos : srcPos+length]
			_dst := dst.Data().([]int8)[dstPos : dstPos+length]
			copy(_dst, _src)
		case []int16:
			_src := src.Data().([]int16)[srcPos : srcPos+length]
			_dst := dst.Data().([]int16)[dstPos : dstPos+length]
			copy(_dst, _src)
		case []uint16: // char
			_src := src.Data().([]uint16)[srcPos : srcPos+length]
			_dst := dst.Data().([]uint16)[dstPos : dstPos+length]
			copy(_dst, _src)
		case []int32:
			_src := src.Data().([]int32)[srcPos : srcPos+length]
			_dst := dst.Data().([]int32)[dstPos : dstPos+length]
			copy(_dst, _src)
		case []int64:
			_src := src.Data().([]int64)[srcPos : srcPos+length]
			_dst := dst.Data().([]int64)[dstPos : dstPos+length]
			copy(_dst, _src)
		case []float32:
			_src := src.Data().([]float32)[srcPos : srcPos+length]
			_dst := dst.Data().([]float32)[dstPos : dstPos+length]
			copy(_dst, _src)
		case []float64:
			_src := src.Data().([]float64)[srcPos : srcPos+length]
			_dst := dst.Data().([]float64)[dstPos : dstPos+length]
			copy(_dst, _src)
		case []*class.Object:
			_src := src.Data().([]*class.Object)[srcPos : srcPos+length]
			_dst := dst.Data().([]*class.Object)[dstPos : dstPos+length]
			copy(_dst, _src)
		default:
			log.Default().Fatalf("error do not known type `%#v` array", t)
		}
	} else {
		panic("error not array")
	}
}

func None(frame *rtda.MethodFrame, mth *class.Method) {
	c := mth.ReturnType()
	stack := frame.OperationStack()
	switch c.Name()[0] {
	case 'V':
	case 'D':
		stack.PushDouble(0)
	case 'F':
		stack.PushFloat(0)
	case 'J':
		stack.PushLong(0)
	case 'L', '[':
		stack.PushRef(&class.Object{})
	default:
		stack.PushInt(0)
	}
}

//javaLangObjectHashCode java/lang/Object.hashCode ()I
func javaLangObject_hashCode(frame *rtda.MethodFrame) {
	obj := frame.GetThis()
	code := int32(uintptr(unsafe.Pointer(obj)))
	frame.OperationStack().PushInt(code)
}

//java/lang/Thread.currentThread ()Ljava/lang/Thread; with DEFAULT zero
func javaLangThread_currentThread(frame *rtda.MethodFrame) {
	frame.OperationStack().PushRef(frame.Thread().JavaThread())
}
